<?php
class Table_model extends Model{
	var $table;
	var $table_name;
	var $id_field;
	
	var $fields;
	var $edit_fields;
	var $field_rules;
	
	/* need to see if these are used and cash them */
	var $view_fields;
	var $related_fields;
	
	var $where;
	var $ci;
	
	/*
	 * new system allows loading a library which can call functions using a set of hooks.
	 * now, any model which wants to process multiple images only needs to add the applicable fields using addDataProcessorField
	 * descendant models can add new data processors using addDataProcessor
	 * 
	 * data processor libraries should extend DataProcessor which sets parent_model to whichever model has been instantiated.  this
	 * allows the libraries to access functions and variables of the current model.
	 * 
	 * TODO: create a data processor for single images, single documents, etc.
	 * 
	 */
	var $data_processor_hooks = array('onEdit', 'onCancel', 'onSave', 'onDelete');
	var $data_processors = array(
		'ProcessMultipleImages'		=> array(
			'onEdit'	=> 'addItemOptions',
			'onCancel'	=> 'removeNewItems',
			'onSave'	=> 'saveNewItems',
			'onDelete'	=> 'removeAllItems'
		),
		'ProcessSingleImage'		=> array(
			'onEdit'	=> 'addItemOption',
			'onCancel'	=> 'removeNewItems',
			'onSave'	=> 'saveNewItem',
			'onDelete'	=> 'removeAllItems'
		),
		'ProcessSingleDocument'		=> array(
			'onEdit'	=> 'addItemOption',
			'onCancel'	=> 'removeNewItems',
			'onSave'	=> 'saveNewItem',
			'onDelete'	=> 'removeAllItems'
		),
		'ProcessMultipleDocuments'	=> array(
			'onEdit'	=> 'addItemOptions',
			'onCancel'	=> 'removeNewItems',
			'onSave'	=> 'saveNewItems',
			'onDelete'	=> 'removeAllItems'
		),
		'ProcessSingleVideo'	=> array(
			'onEdit'	=> 'addItemOption',
			'onCancel'	=> 'removeNewItems',
			'onSave'	=> 'saveNewItem',
			'onDelete'	=> 'removeAllItems'
		)
	);
	
	function Table_model(){
		parent::Model();
		$this->ci = &get_instance();
		$this->ci->load->database();
		$this->edit_fields = $this->view_fields = null;
	}
	
	function processData($hooks, $id = null){
		if(!is_array($hooks)){
			$hooks = array($hooks);
		}
		foreach( $hooks as $hook){
			if( in_array($hook, $this->data_processor_hooks)){
				foreach($this->data_processors as $lib => $d){
					// make sure this hook is set for this data processor
					if( isset($d[$hook])){
						if( !isset($d['_noload']) && isset($d['fields']) && is_array($d['fields']) && count($d['fields'])){
							$lib_call = strtolower($lib);
							if( !isset($this->ci->{$lib_call}) || !is_object($this->ci->{$lib_call})){
								$params = isset($d['libParams']) && is_array($d['libParams'])?$d['libParams']:array();
								$params += array('parent_model' => &$this);
								$this->ci->load->library($lib, $params);
							}
							if( isset($this->ci->{$lib_call}) && is_object($this->ci->{$lib_call}) && is_callable( array($this->ci->{$lib_call}, $d[$hook]))){
								call_user_func_array( array($this->ci->{$lib_call}, $d[$hook]), array( $id, $d['fields']));
							}else{
								trigger_error("could not load library: $lib", E_USER_ERROR);
								$this->data_processors[$lib]['_noload'] = true;
							}
						}
					}
				}
			}
		}
	}
	
	function addDataProcessor($libname, $processor){
		$this->data_processors[$libname] = $processor;
	}
	
	function removeDataProcessor($libname){
		if( isset($this->data_processors[$libname])){
			unset($this->data_processors[$libname]);
		}
	}
	
	function addDataProcessorField($libname, $field, $data = array()){
		$d = null;
		if( isset($this->data_processors[$libname])){
			$d = &$this->data_processors[$libname];
			if( !isset($d['fields']) || !is_array($d['fields'])){
				$d['fields'] = array();
			}
			$d['fields'][$field] = $data;
		}
	}
	
	function addDataProcessorLibParam($libname, $param_name, $param_value){
		$d = null;
		if( isset($this->data_processors[$libname])){
			$d = &$this->data_processors[$libname];
			if( !isset($d['libParams']) || !is_array($d['libParams'])){
				$d['libParams'] = array();
			}
			$d['libParams'][$param_name] = $param_value;
		}
	}
	
	function _isViewableField($e){
		if( is_array($e) ){
			if( isset($e['view']) && $e['view'] ){
				return true;
			}
		}
		return false;
	}
	function getViewFields(){
		return array_filter( $this->fields, array($this, "_isViewableField"));
	}
	
	function _isEditableField($e){
		if( is_array($e)){
			if( isset($e['edit']) && $e['edit'] ){
				return true;
			}
		}
		return false;
	}
	function getEditFields(){
		/*
		 * TODO: caching doesnt work. why?!?!?!?! 
		 if( $this->edit_fields && is_array($this->edit_fields)){
			echo "have cached edit fields: " . intval($this->edit_fields == array_filter( $this->fields, array($this, "_isEditableField")));
		}else{
			$this->edit_fields = array_filter( $this->fields, array($this, "_isEditableField"));
		}*/
		//$ret = ($this->edit_fields?$this->edit_fields:($this->edit_fields = array_filter( $this->fields, array($this, "_isEditableField"))));
		return array_filter( $this->fields, array($this, "_isEditableField"));
	}
	
	function _hasRules($e){
		if( is_array($e) ){
			if( isset($e['rules'])){
				return true;
			}
		}
		return false;
	}
	
	function getValidationFields(){
		return array_filter($this->fields, array($this, '_hasRules'));
	}
	
	/* getRules returns an array of data to pass to the form_validation library */
	function getRules(){
		$rules = array();
		$fields = array_filter($this->fields, array($this, '_hasRules'));
		foreach( $fields as $key => $value){
			$rules[] = array(
				'field'		=> $key,
				'label'		=> (isset($value['name'])?$value['name']:null),
				'rules'		=> $value['rules']
			);
		}
		return $rules;
	}
	
	function _getName($e){
		return isset($e['name'])?$e['name']:null;
	}
	
	function getAllItems( $sort_field, $sort_order, &$items, $archive = 0){
		if( !empty($sort_field) && isset($this->fields[$sort_field]) && $this->fields[$sort_field]['sort'] ){
			$s = $this->fields[$sort_field]['sort'];
			$sort_order = !empty($sort_order)&&in_array($sort_order, array('ASC', 'DESC'))?$sort_order:'ASC';
			if( is_string($s) ){
				$this->db->order_by( "($s)", $sort_order);
			}else{
				$this->db->order_by( $sort_field, $sort_order);
			}
		}
		if( $this->isOrderable() ){
			$this->db->order_by('order_num', 'ASC');
			$this->db->order_by($this->id_field, 'ASC');
		}
		if( $this->hasArchive() ){
			$this->db->where('is_archive', intval($archive));
		}
		if( $this->where ){
			$this->db->where($this->where);
		}
		$items = $this->db->get($this->table)->result_array();
	}
	
	function getAllArchiveItems( $sort_field, $sort_order, &$items, $where = null){
		$this->getAllItems( $sort_field, $sort_order, $items, 1, $where);
	}
	
	function getItem( $id, $array = false){
		$item = null;
		if( $array ){
			$item = $this->db->get_where($this->table, array( $this->id_field => $id), 1)->first_row('array');
		}else{
			$item = $this->db->get_where($this->table, array( $this->id_field => $id), 1)->first_row();
		}
		return $item;
	}
	
	function setItemsLive( $ids, $value = 1){
		if( $this->hasLiveFlag() && is_numeric($value)){
			if( !is_array($ids) ){
				$ids = array($ids);
			}
			if( count($ids) ){
				$this->db->query('UPDATE '.$this->table." SET is_live = $value WHERE ".$this->id_field.' IN ('.implode(',', $ids).')');
			}
		}
	}
	
	function deleteItems( $ids){
		if( !is_array($ids)){
			$ids = array($ids);
		}
		if( count($ids) ){
			$this->db->query('DELETE FROM '.$this->table.' WHERE '.$this->id_field.' IN ('.implode(',', $ids).')');
		}
	}
	
	function archiveItems( $ids, $value = 1){
		if( $this->hasArchive() && is_numeric($value)){
			if( !is_array($ids) ){
				$ids = array($ids);
			}
			if( count($ids) ){
				$this->db->query('UPDATE '.$this->table." SET is_archive = $value WHERE ".$this->id_field.' IN ('.implode(',', $ids).')');
			}
		}
	}
	
	function getEmptyItem( $array = false ){
		$item = null;
		if( !function_exists('array_fill_keys')){
			$this->load->helper('php_funcs');
		}
		if( $array ){
			$item = array_fill_keys(array_keys($this->fields), null);
		}else{
			$item = (object)(array_fill_keys(array_keys($this->fields), null));
		}
		return $item;
	}
	
	function updateItem( $id, &$data){
		$new_data = $this->_onlyTableFieldData($data);
		if( !count($new_data) ){
			return;
		}
		//echo_html_object($new_data);
		$this->db->update( $this->table, $new_data, $this->id_field." = $id");
		//echo $this->db->last_query();
	}
	
	function insertItem( &$data ){
		$new_data = $this->_onlyTableFieldData($data);
		if( !count($new_data) ){
			return;
		}
		$this->db->insert( $this->table, $new_data);
		return $this->db->insert_id();
	}
	
	function saveItem(&$data, $id = null ){
		//$new_data = $this->_onlyFieldData($data);
		if( $id ){
			$this->updateItem($id, $data);
			$this->processData('onSave', $id);
			return $id;
		}else{
			$id = $this->insertItem($data);
			$this->processData('onSave', $id);
			return $id;
		}
	}
	
	function _onlyTableFieldData( &$data ){
		$d = array();
		$fields = $this->db->list_fields($this->table);
		foreach($fields as $field){
			if( isset($data[$field]) ){
				$d[$field] = $data[$field];
			}
		}
		return $d;
	}
	
	function _onlyFieldData( &$data ){
		$d = array();
		$keys = array_diff( array_keys($this->fields), $this->related_fields);
		foreach( $keys as $k){
			$d[$k] = $data[$k];
		}
		return $d;
	}
	
	function saveSortOrder($input = 'sort_order'){
		if( $this->input->post($input) ){
			parse_str($this->input->post($input));
			if( is_array($sortable)){
				//echo_html_object($sortable);
				$this->_saveSortOrder( $sortable, $this->id_field);
			}
		}
	}
	
	function _saveSortOrder( &$sort, $id_field){
		$case = "CASE";
		$i = 0;
		foreach($sort as $id){
			$case .= " WHEN $id_field = $id THEN $i";
			$i++;
		}
		$case .= " ELSE order_num END";
		$sql = "UPDATE {$this->table} SET order_num = $case";
		return $this->db->query($sql);
	}
	
	function isOrderable(){
		return $this->db->field_exists('order_num', $this->table);
	}
	
	function hasArchive(){
		return $this->db->field_exists('is_archive', $this->table);
	}
	
	function hasLiveFlag(){
		return $this->db->field_exists('is_live', $this->table);
	}
	
	function getTableName(){
		return (empty($this->table_name)?$this->table:$this->table_name);
	}
	
	function getTable(){
		return $this->table;
	}
	
	function getIdField(){
		return $this->id_field;
	}
	
	function addEditParameters( $params ){
		foreach( $params as $key => $value){
			if( isset($this->fields[$key]) && isset($this->fields[$key]['edit']) ){
				if( is_array($value) ){
					/*if( !(isset($this->fields[$key]['edit']['params']) && is_array($this->fields[$key]['edit']['params'])) ){
						$this->fields[$key]['edit']['params'] = array();
					}*/
					foreach($value as $v){
						array_push( $this->fields[$key]['edit']['params'], $v);
					}
					//echo_html_object( $this->fields[$key]['edit']);
				}
			}
		}
	}
	
	function getFields(){
		return $this->fields;
	}
	
	function getSaveFields(){
		return array_diff_key( $this->fields, array_fill_keys( $this->related_fields, null));
	}
	
	function getSaveFieldNames(){
		return array_map( array($this, '_getName'), $this->getSaveFields());
	}
	
	function getFieldNames(){
		return array_map( array($this, '_getName'), $this->fields);
	}
	
	function getEditFieldNames(){
		return array_map( array($this, '_getName'), $this->getEditFields());
	}
	
	function deleteItem($id){
		if( $id ){
			$this->deleteItems($id);
			$this->processData('onDelete', $id);
		}
	}
	
	function validateFormInput(){
		$this->load->library('validation');
		$this->validation->set_rules($this->getRules());
		$this->validation->set_fields($this->getSaveFields());
		
		return $this->validation->run();
	}
	
	function isSortable($field_name = 'order_num'){
		return $this->db->field_exists($field_name, $this->table);
	}
	
	function prepareEditFields($id = null){
		/*
		 * stub function for descendant classes
		 */
		$this->processData('onEdit', $id);
	}
	
	function cancelItem($id = null){
		/*
		 * stub function for descendant classes
		 */
		$this->processData('onCancel', $id);
	}
	
	/* should move this to an file_model class above table_model */
	function _parseOptionString($string){
		$temp = null;
		$options = array();
		$temp = explode(';', $string);
		if( ($l=count($temp)) ){
			for($i=0;$i<$l;$i++){
				$key_val = explode('=>', $temp[$i]);
				if(count($key_val)==2){
					$options[$key_val[0]] = $key_val[1];
				}
			}
		}
		return $options;
	}
}
?>